package com.beta.cat.utils.sequence.range.impl.redis;

import com.beta.cat.service.RedissonService;
import com.beta.cat.utils.sequence.exception.SeqException;
import com.beta.cat.utils.sequence.range.SeqRange;
import com.beta.cat.utils.sequence.range.SeqRangeMgr;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RSortedSet;
import org.springframework.util.Assert;

import java.time.Duration;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

/**
 * Redis区间管理器。<br>
 * 说明：为什么要有一个数字为前缀？<br>
 * 原因是：1、为了保证生成的随机数的唯一，需要将所有生成的数据放到redis的Set结构中，便于检验；<br>
 * 2、如果将所有数据都放到一个set中，那么会产生两个问题：set不能过期，永久存在于redis中；另外，这个set里面的值会越来越多，对redis的消耗会越来越大，无法释放。<br>
 * 3、使用一个数字（当前时间到起始点的天数），可以让这个set只存在1天就可以回收掉。前缀设置为4位数，可以使用9999天，即：27年，一个系统一般用不了27年
 */
public class RedisRandomSeqRangeMgr implements SeqRangeMgr {
    //用于算法当前是第几天，方便递增。
    private final LocalDate startDate = LocalDate.of(2024, 1, 1);
    /**
     * 前缀防止key重复
     */
    private final static String KEY_PREFIX = "x_sequence_rnd_";

    /**
     * redis客户端
     */
    private RedissonService redissonService;

    /**
     * 生成的序列号的最大长度
     */
    private int length = 10;
    /**
     * 数字的前缀长度
     */
    private final int numPrefixLen = 5;

    @Override
    public SeqRange nextRange(String name) throws SeqException {
        long days = Math.abs(startDate.until(LocalDate.now(), ChronoUnit.DAYS));
        String pre = StringUtils.leftPad(days + "", numPrefixLen - 1, "0");
        pre = "1" + pre;//防止pre开头是0
        String realKey = getRealKey(name);
        realKey = realKey + "_" + pre;
        RSortedSet set = redissonService.getSet(realKey);

        String num = RandomStringUtils.randomNumeric(length - numPrefixLen);
        while (set.contains(num)) {//如果已经存在，则重新获取
            num = RandomStringUtils.randomNumeric(length - numPrefixLen);
        }
        set.add(num);
        if (set == null || set.size() == 1) {
            redissonService.expire(realKey, Duration.ofDays(1));
        }

        Long min = Long.valueOf(pre + num);//真实数字
        Long max = min + 1;
        return new SeqRange(min, max);
    }

    @Override
    public void init() {
        Assert.isTrue(length > 5, "length must be greater than 5");
        Assert.notNull(redissonService, "redissonService is null");
    }

    public RedissonService getRedissonService() {
        return redissonService;
    }

    public void setRedissonService(RedissonService redissonService) {
        this.redissonService = redissonService;
    }


    private String getRealKey(String name) {
        return KEY_PREFIX + name;
    }

    public int getLength() {
        return length;
    }

    public void setLength(int length) {
        this.length = length;
    }
}
